﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace CountJobTags {
    /// <summary>
    /// A ComboBox that allows user to check multiple items, also has a context menu with options to select/de-select all/invert
    /// </summary>
    public partial class CheckableComboBox : UserControl {
        /// <summary>
        /// Event prototype for when user 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public delegate void CheckableComboBoxFocusDlgt(object sender, FocusChangeEventArgs e);
        /// <summary>Event raised when the control loses focus</summary>
        public event CheckableComboBoxFocusDlgt FocusLost;
        /// <summary>Event raised when the control gains focus</summary>
        public event CheckableComboBoxFocusDlgt Focused;
        /// <summary>
        /// Event prototype for when the control wants to raise a suggestion event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public delegate void CheckableComboBoxSuggestionDlgt(object sender, SuggestionEventArgs e);
        /// <summary>
        /// Event raised when the control has a new suggestion
        /// </summary>
        public event CheckableComboBoxSuggestionDlgt UsageSuggestionChanged;

        private int _CheckChangedCount;
        private bool _InvertSuggestionDisplayed;
        private List<SelectableCaption> _SelectableCaptionList;
        private SelectableCaption _DisplayItem;

        #region Constructor
        /// <summary>
        /// Default constructor
        /// </summary>
        public CheckableComboBox() {
            InitializeComponent();
            _CheckChangedCount = 0;
            _InvertSuggestionDisplayed = false;
            _SelectableCaptionList = new List<SelectableCaption>();
        }
        #endregion

        #region Event handlers
        /// <summary>
        /// Fires when user uses the context menu associated with a combo box, selects all
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuSelectAll_Click(object sender, RoutedEventArgs e) {
            MenuItem mnu = sender as MenuItem;
            ComboBox cboParent = ((ContextMenu)mnu.Parent).PlacementTarget as ComboBox;
            foreach (SelectableCaption aCap in cboParent.Items) {
                if (aCap != _DisplayItem) {
                    aCap.IsSelected = true;
                }
            }
            RaiseSelectionChangedEvent();
        }

        /// <summary>
        /// Unselects all the items in the combobox
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuUnSelectAll_Click(object sender, RoutedEventArgs e) {
            MenuItem mnu = sender as MenuItem;
            ComboBox cboParent = ((ContextMenu)mnu.Parent).PlacementTarget as ComboBox;
            foreach (SelectableCaption aCap in cboParent.Items) {
                if (aCap != _DisplayItem) {
                    aCap.IsSelected = false;
                }
            }
            RaiseSelectionChangedEvent();
        }

        /// <summary>
        /// Inverts the selection in the combo box
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuInvertSelection_Click(object sender, RoutedEventArgs e) {
            MenuItem mnu = sender as MenuItem;
            ComboBox cboParent = ((ContextMenu)mnu.Parent).PlacementTarget as ComboBox;
            int selCount = 0;
            foreach (SelectableCaption aCap in cboParent.Items) {
                if (aCap != _DisplayItem) {
                    aCap.IsSelected = !aCap.IsSelected;
                    if (aCap.IsSelected) selCount++;
                }
            }
            if (selCount == 0)
                OnUsageSuggestionChanged("Selection cleared");
            else {
                OnUsageSuggestionChanged("Filtering on " + selCount.ToString() + " items");
            }
            RaiseSelectionChangedEvent();
        }

        private void cboChecked_LostFocus(object sender, RoutedEventArgs e) {
            if (FocusLost != null) {
                FocusLost(this, new FocusChangeEventArgs());
            }
        }

        private void cboChecked_GotFocus(object sender, RoutedEventArgs e) {
            if (Focused != null) {
                Focused(this, new FocusChangeEventArgs());
            }
        }
        #endregion


        #region Events to be consumed by caller
        /// <summary>Raised when the value is changed</summary>
        public static readonly RoutedEvent SelectionChangedEvent = EventManager.RegisterRoutedEvent("SelectionChanged",
                                RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(CheckableComboBox));

        /// <summary>Provide CLR accessors for the event</summary>
        public event RoutedEventHandler SelectionChanged {
            add { AddHandler(SelectionChangedEvent, value); }
            remove { RemoveHandler(SelectionChangedEvent, value); }
        }

        /// <summary>This method raises the ValueChanged event</summary>
        void RaiseSelectionChangedEvent() {
            RoutedEventArgs newEventArgs = new RoutedEventArgs(CheckableComboBox.SelectionChangedEvent);
            RaiseEvent(newEventArgs);
        }
        #endregion

        /// <summary>
        /// Allows the checkbox source to be specified
        /// </summary>
        public IEnumerable<SelectableCaption> ItemsSource {
            get {
                if (_SelectableCaptionList == null) {
                    _SelectableCaptionList = new List<SelectableCaption>();
                }
                return _SelectableCaptionList;
            }
            set {
                _SelectableCaptionList = value as List<SelectableCaption>;
                foreach (SelectableCaption cap in value) {
                    cap.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Caption_PropertyChanged);
                }
                _DisplayItem = new SelectableCaption { Caption = SelectionSummary, IsSelected = false, IsNotDisplaySummary = false };
                _SelectableCaptionList.Add(_DisplayItem);
                cboChecked.ItemsSource = value;
                cboChecked.SelectedItem = _DisplayItem;
            }
        }

        /// <summary>
        /// We catch the change event for every checkbox so we can display a count
        /// </summary>
        /// <param name="sender">A SelectableCaption</param>
        /// <param name="e">EventArgs</param>
        void Caption_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
            if (e.PropertyName == "IsSelected") {
                if (++_CheckChangedCount >= 3 && _CheckChangedCount <= 4) {
                    string message = _CheckChangedCount == 3
                        ? "Try using the context menu item 'Invert Selection'"
                        : "Suggestion: Un-Check the items you want, then use context menu to invert (faster)";
                    OnUsageSuggestionChanged(message);
                }
                _DisplayItem.Caption = SelectionSummary;
                cboChecked.SelectedItem = _DisplayItem;
                RaiseSelectionChangedEvent();
            }
        }

        /// <summary>
        /// Here we raise the event tha the suggestion has changed
        /// </summary>
        /// <param name="message"></param>
        protected void OnUsageSuggestionChanged(string message) {
            if (UsageSuggestionChanged != null) {
                UsageSuggestionChanged(this, new SuggestionEventArgs { Suggestion = message });
            }
        }

        /// <summary>
        /// Gets all the captions that user has selected with a check
        /// </summary>
        public List<string> SelectedCaptions {
            get {
                List<string> result = ((IEnumerable<SelectableCaption>)cboChecked.ItemsSource).Where(c => c.IsSelected).Select(c => c.Caption).ToList<string>();
                return result;
            }
        }

        /// <summary>
        /// Displays a string explaining what items have been checked
        /// </summary>
        public string SelectionSummary {
            get {
                string result = string.Empty;
                List<string> selectedCaptions = _SelectableCaptionList
                                .Where(c => c.IsSelected)
                                .Select(c => c.Caption)
                                .ToList<string>();
                if (selectedCaptions.Count == 0) {
                    result = "Unfiltered";
                } else if (selectedCaptions.Count == 1) {
                    result = "Filtered by " + selectedCaptions[0];
                } else if (selectedCaptions.Count == 2) {
                    result = "Filtered by " + SelectedCaptions[0] + " and " + selectedCaptions[1];
                } else {
                    result = selectedCaptions.Count.ToString() + " items selected";
                }

                return result;
            }
        }

        /// <summary>
        /// When the combo box drops, try displaying a hint by raising the event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cboChecked_DropDownOpened(object sender, EventArgs e) {
            if (UsageSuggestionChanged != null && !_InvertSuggestionDisplayed) {
                UsageSuggestionChanged(this, new SuggestionEventArgs { Suggestion = "Hint: right-click for context menu, try Inverting your selection!" });
                _InvertSuggestionDisplayed = true;
            }
        }

        private void cboChecked_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if (e.AddedItems.Count == 1 && e.AddedItems[0] != _DisplayItem) {
                cboChecked.SelectedItem = _DisplayItem;
            }
        }
    }

    /// <summary>
    /// Custom event args
    /// </summary>
    public class SuggestionEventArgs : EventArgs {
        /// <summary>A suggestion for the user</summary>
        public string Suggestion { get; set; }
    }

    /// <summary>
    /// It seems like I've been forced to declare my own event args but I have no special properties to attach
    /// </summary>
    public class FocusChangeEventArgs : EventArgs {
    }
}
